﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using ZedGraph;
using System.IO;

namespace CPS1
{
    public partial class SignalWindow : Form
    {
        private MainWindow parent;
        private Signal selectedSignal; // sygnał analogowy
        private SinSignal aliasedSignal; // przykładowy sygnał fd dla zobrazowania aliasingu
        private DiscreetSignal probeSignal; // sygnał spróbkowany
        private QuantizatedSignal qsignal; // sygnał skwantyzowany
        private DiscreetSignal convSignal; // sygnał zrekonstruowany

        public SignalWindow(MainWindow parent, Signal selectedSignal)
        {
            InitializeComponent();
         
            this.parent = parent;
            this.selectedSignal = selectedSignal;
            this.qsignal = null;

            this.probeSignal = new DiscreetSignal(this.selectedSignal.name, this.selectedSignal.A, this.selectedSignal.t1, this.selectedSignal.f, this.selectedSignal.calculatedValues);
            
            this.drawSignal();
            
            this.originalFrequency.Text = (1.0 / this.selectedSignal.T).ToString();
        }

        private void drawSignal()
        {
            zgc_wykres.GraphPane = new GraphPane(new RectangleF(0, 0, 615, 442), this.selectedSignal.name, "A", "T[s]");
            zgc_wykres.SetBounds(8, 86, 615, 442);
            GraphPane myPane = zgc_wykres.GraphPane;
            zgc_wykres.Refresh();

            // Set the titles and axis labels
            myPane.Title.Text = selectedSignal.name;
            myPane.XAxis.Title.Text = "t[s]";
            myPane.YAxis.Title.Text = "A";

            if (checkBox1.Checked)
            {
                // Make up some data arrays based on the Sine function
                PointPairList list = new PointPairList();
                int j = 0;
                Double i = 0;
                while (i < selectedSignal.t1 + selectedSignal.d && j < selectedSignal.calculatedValues.Length / 2)
                {
                    if (i < selectedSignal.t1)
                    {
                        list.Add(i, 0.0);
                    }
                    else
                    {
                        list.Add(selectedSignal.calculatedValues[j, 0], selectedSignal.calculatedValues[j, 1]);
                        j++;
                    }

                    i = Math.Round(i + (1.0 / selectedSignal.f), 9);
                }

                LineItem myCurve = myPane.AddCurve("Signal", list, Color.Green, SymbolType.None);
            }

            if (checkBox3.Checked) 
            {
                if (typeof(SinSignal) == this.selectedSignal.GetType())
                {
                    int probefrequency = Int32.Parse(probeFrequence.Text);
                    Double newf = (1.0 / this.selectedSignal.T) + (1 * probefrequency);
                    Double newT = 1.0 / newf;

                    this.aliasedSignal = new SinSignal(this.selectedSignal.A, newT, this.selectedSignal.t1, this.selectedSignal.d, this.selectedSignal.f * 10, this.selectedSignal.name);
                    this.aliasedSignal.calculateSignal();
                    // Make up some data arrays based on the Sine function
                    PointPairList list = new PointPairList();
                    int j = 0;
                    Double i = 0;

                    while (i < this.aliasedSignal.t1 + this.aliasedSignal.d && j < this.aliasedSignal.calculatedValues.Length / 2)
                    {
                        if (i < this.aliasedSignal.t1)
                        {
                            list.Add(i, 0.0);
                        }
                        else
                        {
                            list.Add(this.aliasedSignal.calculatedValues[j, 0], this.aliasedSignal.calculatedValues[j, 1]);
                            j++;
                        }

                        i = Math.Round(i + (1.0 / this.aliasedSignal.f), 9);
                    }

                    LineItem myCurve = myPane.AddCurve("Signal", list, Color.Blue, SymbolType.None);
                }
                else
                {
                    MessageBox.Show("Aliasing jest obsługiwany wyłącznie dla sygnału sinusoidalnego");
                }
            }

            if (checkBox2.Checked) 
            {
                int probefrequency = Int32.Parse(probeFrequence.Text);

                PointPairList list = new PointPairList();
                Double i = 0;
                int j = 0;
                Double[,] probeCalculatedValues = new Double[(int)(selectedSignal.d * probefrequency), 2];

                while (i < selectedSignal.t1 + selectedSignal.d && j < selectedSignal.calculatedValues.Length / 2)
                {
                    if (i < selectedSignal.t1)
                    {
                        list.Add(i, 0.0);
                    }
                    else
                    {
                        if (j < probeCalculatedValues.Length / 2)
                        {
                            probeCalculatedValues[j, 0] = i;
                            probeCalculatedValues[j, 1] = selectedSignal.value(i);
                            list.Add(i, selectedSignal.value(i));
                            j++;
                        }
                    }

                    i = Math.Round(i + (1.0 / probefrequency), 9);
                }

                this.probeSignal = new DiscreetSignal(this.selectedSignal.name, this.selectedSignal.A, this.selectedSignal.t1, probefrequency, probeCalculatedValues);

                LineItem myCurve = myPane.AddCurve("Signal", list, Color.Firebrick, SymbolType.Diamond);
                myCurve.Line.IsVisible = false;
                myCurve.Symbol.Border.IsVisible = false;
                myCurve.Symbol.Fill = new Fill(Color.Firebrick);
            }
            
            myPane.Legend.IsVisible = false;

            // Set the Y axis intersect the X axis at an X value of 0.0
            myPane.YAxis.Cross = 0.0;
            // Turn off the axis frame and all the opposite side tics
            myPane.Chart.Border.IsVisible = false;
            myPane.XAxis.MajorTic.IsOpposite = false;
            myPane.XAxis.MinorTic.IsOpposite = false;
            myPane.YAxis.MajorTic.IsOpposite = false;
            myPane.YAxis.MinorTic.IsOpposite = false;

            // Calculate the Axis Scale Ranges
            zgc_wykres.AxisChange();
            zgc_wykres.Refresh();
            tabPage1.Refresh();
        }
        //rysowanie skwantyzowanego sygnału
        private void drawDigitalSignal(Signal qsignal)
        {
            dig_wykres.GraphPane = new GraphPane(new RectangleF(0, 0, 615, 442), qsignal.name, "A", "T[s]");
            dig_wykres.SetBounds(8, 86, 615, 442);
            GraphPane myPane = dig_wykres.GraphPane;

            // Set the titles and axis labels
            myPane.Title.Text = qsignal.name;
            myPane.XAxis.Title.Text = "t[s]";
            myPane.YAxis.Title.Text = "A";

            if (checkBox5.Checked)
            {
                // Make up some data arrays based on the Sine function
                PointPairList list = new PointPairList();
                int j = 0;
                Double i = 0;
                while (i < probeSignal.t1 + probeSignal.d && j < probeSignal.calculatedValues.Length / 2)
                {
                    if (i < probeSignal.t1)
                    {
                        list.Add(i, 0.0);
                    }
                    else
                    {
                        list.Add(probeSignal.calculatedValues[j, 0], probeSignal.calculatedValues[j, 1]);
                        j++;
                    }

                    i = Math.Round(i + (1.0 / probeSignal.f), 9);
                }

                LineItem myCurve = myPane.AddCurve("Signal", list, Color.Green, SymbolType.XCross);
                myCurve.Line.IsVisible = false;
                myCurve.Symbol.Border.IsVisible = true;
                myCurve.Symbol.Fill = new Fill(Color.Green);
            }

            if (checkBox4.Checked)
            {
                // Make up some data arrays based on the Sine function
                PointPairList list = new PointPairList();
                int j = 0;
                Double i = 0;
                while (i < qsignal.t1 + qsignal.d && j < qsignal.calculatedValues.Length / 2)
                {
                    if (i < qsignal.t1)
                    {
                        list.Add(i, 0.0);
                    }
                    else
                    {
                        list.Add(qsignal.calculatedValues[j, 0], qsignal.calculatedValues[j, 1]);
                        j++;
                    }

                    i = Math.Round(i + (1.0 / qsignal.f), 9);
                }

                LineItem myCurve = myPane.AddCurve("Signal", list, Color.Green, SymbolType.Diamond);
                myCurve.Line.IsVisible = false;
                myCurve.Symbol.Border.IsVisible = false;
                myCurve.Symbol.Fill = new Fill(Color.Firebrick);
            }

            myPane.Legend.IsVisible = false;

            // Set the Y axis intersect the X axis at an X value of 0.0
            myPane.YAxis.Cross = 0.0;
            // Turn off the axis frame and all the opposite side tics
            myPane.Chart.Border.IsVisible = false;
            myPane.XAxis.MajorTic.IsOpposite = false;
            myPane.XAxis.MinorTic.IsOpposite = false;
            myPane.YAxis.MajorTic.IsOpposite = false;
            myPane.YAxis.MinorTic.IsOpposite = false;

            // Calculate the Axis Scale Ranges
            dig_wykres.AxisChange();
            dig_wykres.Refresh();
        }

        private void button3_Click(object sender, EventArgs e)
        {
            if (cb_conversion.SelectedIndex == 0)
            {
                if (cbType.SelectedIndex == 0)
                {
                    this.calculate_ext_0(this.probeSignal);
                    drawCASignal(this.convSignal);
                    calculateSimilarSignals();
                }
                else
                {
                    if (this.qsignal != null)
                    {
                        this.calculate_ext_0(this.qsignal);
                        drawCASignal(this.convSignal);
                        calculateSimilarSignals();
                    }
                    else
                    {
                        MessageBox.Show("Najpierw skwantyzuj sygnał");
                    }
                }
            }
            else 
            {
                if (cbType.SelectedIndex == 0)
                {
                    this.calculate_sinc(this.probeSignal);
                    drawCASignal(this.convSignal);
                    calculateSimilarSignals();
                }
                else
                {
                    if (this.qsignal != null)
                    {
                        this.calculate_sinc(this.qsignal);
                        drawCASignal(this.convSignal);
                        calculateSimilarSignals();
                    }
                    else
                    {
                        MessageBox.Show("Najpierw skwantyzuj sygnał");
                    }
                }
            }
        }
        // przycisk od kwantyzacji
        private void button1_Click(object sender, EventArgs e)
        {
            int bitAmount =  Int16.Parse(tb_bity.Text);
            //
            if (bitAmount <= 0)
            {
                MessageBox.Show("Proszę podać wartość większą od 1");   
            }
            else
            {
                Refresh();
                this.qsignal = new QuantizatedSignal(this.probeSignal, bitAmount);
                drawDigitalSignal(this.qsignal);
            }
        }

        private void cb_conversion_SelectedIndexChanged(object sender, EventArgs e)
        {
            string op = "";
            if (cb_conversion.SelectedIndex == 0) op = "rzad_0"; // esktrpolacja 0-wego rzędu
            else  op = "sinc"; // funkcja sinc
        }

        //konwersja C/A
        private void drawCASignal(Signal qsignal)
        {
            ca_wykres.GraphPane = new GraphPane(new RectangleF(0, 0, 615, 442), this.convSignal.name, "A", "T[s]");
            ca_wykres.SetBounds(8, 86, 615, 442);
            GraphPane myPane = ca_wykres.GraphPane;

            // Set the titles and axis labels
            myPane.Title.Text = this.convSignal.name;
            myPane.XAxis.Title.Text = "t[s]";
            myPane.YAxis.Title.Text = "A";

            if (checkBox6.Checked)
            {
                // Make up some data arrays based on the Sine function
                PointPairList list = new PointPairList();
                int j = 0;
                Double i = 0;
                while (j < this.convSignal.calculatedValues.Length / 2)
                {
                    if (i < this.convSignal.t1)
                    {
                        list.Add(i, 0.0);
                    }
                    else
                    {
                        if (this.convSignal.calculatedValues[j, 0] > 0 || j == 0)
                        {
                            list.Add(this.convSignal.calculatedValues[j, 0], this.convSignal.calculatedValues[j, 1]);
                        }
                        j++;
                    }

                    i = Math.Round(i + (1.0 / this.convSignal.f), 9);
                }

                LineItem myCurve = myPane.AddCurve("Signal", list, Color.Orange, SymbolType.None);
                myCurve.Line.IsVisible = true;
            }

            if (checkBox7.Checked) 
            {
                // Make up some data arrays based on the Sine function
                PointPairList list = new PointPairList();
                int j = 0;
                Double i = 0;
                while (j < this.selectedSignal.calculatedValues.Length / 2)
                {
                    if (i < this.selectedSignal.t1)
                    {
                        list.Add(i, 0.0);
                    }
                    else
                    {
                        list.Add(this.selectedSignal.calculatedValues[j, 0], this.selectedSignal.calculatedValues[j, 1]);
                        j++;
                    }

                    i = Math.Round(i + (1.0 / this.selectedSignal.f), 9);
                }

                LineItem myCurve = myPane.AddCurve("Signal", list, Color.Green, SymbolType.None);
                myCurve.Line.IsVisible = true;
            }

            myPane.Legend.IsVisible = false;

            // Set the Y axis intersect the X axis at an X value of 0.0
            myPane.YAxis.Cross = 0.0;
            // Turn off the axis frame and all the opposite side tics
            myPane.Chart.Border.IsVisible = false;
            myPane.XAxis.MajorTic.IsOpposite = false;
            myPane.XAxis.MinorTic.IsOpposite = false;
            myPane.YAxis.MajorTic.IsOpposite = false;
            myPane.YAxis.MinorTic.IsOpposite = false;

            // Calculate the Axis Scale Ranges
            ca_wykres.AxisChange();
            ca_wykres.Refresh();
        }
        // rysowanie wykresów przy próbkowaniu
        private void button2_Click(object sender, EventArgs e)
        {
            drawSignal();
        }
        // obliczanie sygnału dokładnego (10 razy większa częstotliwość próbkowania) dla 
        // ekstrapolacji zerowego rzędu
        private void calculate_ext_0(Signal sign) { 
            // przygotowanie 10 razy większej tabeli
            Double[,] calculatedValuesApprox = new Double[(sign.calculatedValues.Length * 10) / 2, 2];
            // obliczenie wartości funkcji dla nowych wartości
            for (int i = 0; i < (sign.calculatedValues.Length / 2); i++ )
            {
                if (i < (sign.calculatedValues.Length / 2) - 1)
                {
                    // wygenerowanie 10 wartości dla przedziału
                    for (int j = 0; j < 10; j++)
                    {
                        calculatedValuesApprox[(i * 10) + j, 0] = sign.calculatedValues[i, 0] + (j * (1.0 / (sign.f * 10))); // czas
                        calculatedValuesApprox[(i * 10) + j, 1] = sign.calculatedValues[i, 1];
                    }
                }
                else
                {
                    // wygenerowanie 10 wartości dla przedziału
                    for (int j = 0; j < 10; j++)
                    {
                        calculatedValuesApprox[(i * 10) + j, 0] = sign.calculatedValues[i, 0] + (j * (1.0 / (sign.f * 10))); // czas
                        calculatedValuesApprox[(i * 10) + j, 1] = sign.calculatedValues[i, 1];
                    }
                }
               // calculatedValuesApprox[j, 0] = i; // czas
               // if (i <= sign.calculatedValues[k, 0])
               // {
               //     calculatedValuesApprox[j, 1] = sign.calculatedValues[k, 1]; // wartość
               // }
               // else
               // {
               //     if (k < (sign.calculatedValues.Length / 2) - 1)
               //     {
               //         k++;
               //     }

               //     calculatedValuesApprox[j, 1] = sign.calculatedValues[k, 1];
               // }
               // j++;
               // i = Math.Round(i + ((double)1.0 / (double)newF), 6); // zwiększamy wartość czasu o czas próbkowania 
            }

            this.convSignal = new DiscreetSignal(sign.name, sign.A, sign.t1, sign.f * 10, calculatedValuesApprox);
        }
        // obliczanie sygnału dokładnego (10 razy większa częstotliwość próbkowania dla 
        // interpolacji z użyciem funkcji sinc
        private void calculate_sinc(Signal sign)
        {
            // odczyt ilości uwzględnianych próbek
            int probeAmount = Int32.Parse(interpolationProbesAmount.Text);
            // przygotowanie 10 razy większej tabeli
            Double[,] calculatedValuesApprox = new Double[(sign.calculatedValues.Length * 10) / 2, 2];
            Double newF = sign.f * 10; // 10 razy większa częstotliwość
            // obliczenie wartości funkcji dla nowych wartości
            Double i = sign.t1;
            int j = 0;
            int k = 0;

            while (i < sign.t1 + sign.d && j < calculatedValuesApprox.Length / 2)
            {
                calculatedValuesApprox[j, 0] = i; // czas
                if (i == sign.calculatedValues[k, 0] && k < (sign.calculatedValues.Length / 2) - 1)
                {
                    k++;
                }

                Double T_s = (1 / sign.f);
                Double value = 0.0;

                for (int x = -probeAmount; x <= probeAmount; x++)
                {
                    int offset = k + x;

                    if (offset >= 0 && offset < sign.calculatedValues.Length / 2)
                    {
                        value += sign.calculatedValues[offset, 1] * sinc(i / T_s - offset);
                    }
                }

                calculatedValuesApprox[j, 1] = value;
                j++;
                i = Math.Round(i + ((double)1.0 / (double)newF), 9); // zwiększamy wartość czasu o czas próbkowania 
            }

            this.convSignal = new DiscreetSignal(sign.name, sign.A, sign.t1, sign.f * 10, calculatedValuesApprox);
        }

        private Double sinc(Double t) {
            if (t == 0) return 1.0;
            else return (Math.Sin(Math.PI * t) / (Math.PI * t));
        }

        private void calculateSimilarSignals() 
        {
            int N = (int) (this.convSignal.calculatedValues.Length / 2);
            //
            // MSE
            //
            Double MSE = 0.0;
            //
            for (int i = 0; i < N - 1; i++) 
            {
                MSE += Math.Pow(convSignal.calculatedValues[i, 1] - this.selectedSignal.value(convSignal.calculatedValues[i, 0]), 2);
            }
            //
            MSE = (1.0 / N) * MSE; 
            //
            // PSNR
            //
            Double PSNR = 0.0;
            Double maxPSNR = -10000.0;
            // 
            for (int i = 0; i < N; i++) 
            {
                if (convSignal.calculatedValues[i, 1] > maxPSNR) 
                {
                    maxPSNR = convSignal.calculatedValues[i, 1];
                } 
            }
            //
            PSNR = 10 * Math.Log10(maxPSNR / MSE);
            //
            // SNR
            //
            Double SNR = 0.0;
            Double sumX2 = 0.0;

            for (int i = 0; i < N - 1; i++)
            {
                sumX2 += Math.Pow(convSignal.calculatedValues[i, 1], 2);
            }

            SNR = 10 * Math.Log10(sumX2 / (N * MSE));
            //
            // MD
            //
            Double MD = 0.0;
            //
            for (int i = 0; i < N - 1; i++)
            {
                if (Math.Abs(convSignal.calculatedValues[i, 1] - this.selectedSignal.value(convSignal.calculatedValues[i, 0])) > MD)
                {
                    MD = Math.Abs(convSignal.calculatedValues[i, 1] - this.selectedSignal.value(convSignal.calculatedValues[i, 0]));
                }
            }
            // fill the fields
            mse_value.Text = MSE.ToString();
            psnr_value.Text = PSNR.ToString();
            snr_value.Text = SNR.ToString();
            md_value.Text = MD.ToString();
        }
    }
}
